/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.rest.support;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.AttachmentConfig;
import com.eviware.soapui.config.CompressedStringConfig;
import com.eviware.soapui.config.CredentialsConfig;
import com.eviware.soapui.config.HttpRequestConfig;
import com.eviware.soapui.config.OldRestRequestConfig;
import com.eviware.soapui.config.RestParameterConfig;
import com.eviware.soapui.config.RestParametersConfig;
import com.eviware.soapui.config.RestRequestConfig;
import com.eviware.soapui.config.RestRequestStepConfig;
import com.eviware.soapui.config.RestResourceRepresentationConfig;
import com.eviware.soapui.config.SettingsConfig;
import com.eviware.soapui.config.StringToStringMapConfig;
import com.eviware.soapui.config.StringToStringMapConfig.Entry;
import com.eviware.soapui.config.TestAssertionConfig;
import com.eviware.soapui.impl.rest.RestMethod;
import com.eviware.soapui.impl.rest.RestRepresentation;
import com.eviware.soapui.impl.rest.RestRequest;
import com.eviware.soapui.impl.rest.RestRequestInterface;
import com.eviware.soapui.impl.rest.RestResource;
import com.eviware.soapui.impl.rest.RestService;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.teststeps.RestTestRequestStep;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.project.Project;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Convert old-style REST Requests to new ones, creating REST Methods for them
 * or placing them into existing Methods.
 *
 * @author Dain Nilsson
 */
public class RestRequestConverter {

    private static Map<Project, Boolean> autoConvert = new HashMap<Project, Boolean>();
    private final static Logger log = Logger.getLogger(RestRequestConverter.class);

    public static void convert(RestResource resource, OldRestRequestConfig oldConfig) {
        convert(resource, oldConfig, getMethod(resource, oldConfig.getMethod(), oldConfig.getName()));
    }

    public static RestMethod getMethod(RestResource resource, String methodType, String requestName) {
        WsdlProject project = resource.getService().getProject();
        if (!autoConvert.containsKey(project)) {
            autoConvert
                    .put(project,
                            UISupport
                                    .confirm(
                                            "The model for REST requests has changed slightly,\r\n"
                                                    + "introducing a new REST Method item in-between each REST Resource and Request.\r\n"
                                                    + "Any existing REST Request must now be placed under either an existing Method or a new one, "
                                                    + "either automatically or manually.\r\n\r\nWould You like SoapUI to do this automatically using the default values?",
                                            "Update REST model for project: " + project.getName()));
        }
        RestMethod method = null;
        List<String> options = new ArrayList<String>();
        for (int c = 0; c < resource.getRestMethodCount(); c++) {
            RestMethod restMethod = resource.getRestMethodAt(c);
            if (restMethod.getMethod().toString().equals(methodType)) {
                options.add(restMethod.getName());
            }
        }
        if (autoConvert.get(project)) {
            if (options.size() > 0) {
                method = resource.getRestMethodByName(options.get(0));
                log.info("Placed request '" + requestName + "' under method '" + method.getName() + "' in Resource '"
                        + resource.getName() + "'.");
            } else {
                method = resource.addNewMethod(methodType + " Method");
                method.setMethod(RestRequestInterface.HttpMethod.valueOf(methodType));
                log.info("Created new Method for Resource '" + resource.getName() + "'.");
            }
        } else {
            options.add("[Create new REST Method]");
            if (requestName == null) {
                requestName = "REST Request";
            }
            String message = "Select REST Method to place \"" + resource.getName() + " > " + requestName + "\" under.";
            Object op = UISupport.prompt(message, "Migrate REST Request", options.toArray());
            if (op != null) {
                int ix = options.indexOf(op);
                if (ix != -1 && ix != options.size() - 1) {
                    method = resource.getRestMethodByName((String) op);
                }
            } else {
                throw new RestConversionException("Cannot get RestMethod selection!");
            }
            if (method == null) {
                String name = UISupport.prompt("Name for REST " + methodType + " Method", "Create new REST Method",
                        methodType + " Method");
                if (name == null) {
                    throw new RestConversionException("Cannot get name for RestMethod!");
                }
                method = resource.addNewMethod(name);
                method.setMethod(RestRequestInterface.HttpMethod.valueOf(methodType));
            }
        }
        return method;
    }

    public static RestResource resolveResource(RestTestRequestStep requestStep) {
        Map<String, RestResource> options = new LinkedHashMap<String, RestResource>();

        WsdlProject project = requestStep.getTestCase().getTestSuite().getProject();
        String serviceName = requestStep.getRequestStepConfig().getService();
        RestService service = (RestService) project.getInterfaceByName(serviceName);
        if (service != null) {
            addResources(service, options);
        } else {
            for (Interface iface : project.getInterfaceList()) {
                if (iface instanceof RestService) {
                    addResources((RestService) iface, options);
                }
            }
        }
        options.put("<Delete TestRequest>", null);

        String message = "Select a new REST Resource to place TestRequest \"" + requestStep.getName() + "\" under.";
        Object op = UISupport.prompt(message, "Missing REST Resource for TestRequest", options.keySet().toArray());
        RestResource resource = options.get(op);

        return resource;
    }

    private static void addResources(RestService service, Map<String, RestResource> list) {
        for (RestResource resource : service.getResources().values()) {
            list.put(service.getName() + " > " + resource.getName(), resource);
        }
    }

    @SuppressWarnings("deprecation")
    private static void convert(RestResource resource, OldRestRequestConfig oldConfig, RestMethod method) {

        RestRequest request = method.addNewRequest(oldConfig.getName());

        XmlBeansRestParamsTestPropertyHolder params = new XmlBeansRestParamsTestPropertyHolder(null,
                oldConfig.getParameters());
        RestParamsPropertyHolder parentParams = method.getOverlayParams();

        for (TestProperty prop : params.values()) {
            if (!parentParams.containsKey(prop.getName())) {
                method.getParams().addParameter((RestParamProperty) prop);
            }
            request.setPropertyValue(prop.getName(), prop.getValue());
        }
        params.release();

        boolean exists;
        for (RestResourceRepresentationConfig rep : oldConfig.getRepresentationList()) {
            exists = false;
            for (RestRepresentation existing : method.getRepresentations(
                    RestRepresentation.Type.valueOf(rep.getType().toString()), rep.getMediaType())) {
                if (existing.getElement() == null && rep.getElement() == null
                        || existing.getElement().equals(rep.getElement())) {
                    exists = true;
                    break;
                }
            }
            if (!exists) {
                RestRepresentation repr = method.addNewRepresentation(RestRepresentation.Type.valueOf(rep.getType()
                        .toString()));
                repr.setConfig((RestResourceRepresentationConfig) rep.copy());
            }
        }

        RestRequestConfig newConfig = request.getConfig();

        newConfig.setRequest(oldConfig.getRequest());

        for (AttachmentConfig ac : oldConfig.getAttachmentList()) {
            try {
                if (ac.isSetData()) {
                    File temp = File.createTempFile("pattern", ".suffix");
                    temp.deleteOnExit();
                    FileOutputStream out = new FileOutputStream(temp);
                    out.write(ac.getData());
                    request.attachFile(temp, true);
                } else {
                    request.attachFile(new File(ac.getUrl()), false);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        newConfig.setAttachmentArray(oldConfig.getAttachmentArray());

        if (oldConfig.isSetFullPath()) {
            newConfig.setFullPath(oldConfig.getFullPath());
        }
        if (oldConfig.isSetMediaType()) {
            newConfig.setMediaType(oldConfig.getMediaType());
        }
        if (oldConfig.isSetPostQueryString()) {
            newConfig.setPostQueryString(oldConfig.getPostQueryString());
        }
        if (oldConfig.isSetAccept()) {
            newConfig.setAccept(oldConfig.getAccept());
        }
        if (oldConfig.isSetDescription()) {
            newConfig.setDescription(oldConfig.getDescription());
        }
        if (oldConfig.isSetId()) {
            newConfig.setId(oldConfig.getId());
        }
        if (oldConfig.isSetSettings()) {
            newConfig.setSettings((SettingsConfig) oldConfig.getSettings().copy());
        }
        if (oldConfig.isSetSslKeystore()) {
            newConfig.setSslKeystore(oldConfig.getSslKeystore());
        }
        if (oldConfig.isSetTimestamp()) {
            newConfig.setTimestamp(oldConfig.getTimestamp());
        }
        if (oldConfig.isSetWadlId()) {
            newConfig.setWadlId(oldConfig.getWadlId());
        }

        request.updateConfig(newConfig);

    }

    public static HttpRequestConfig convert(OldRestRequestConfig old) {
        HttpRequestConfig config = HttpRequestConfig.Factory.newInstance();
        config.setAssertionArray(old.getAssertionList().toArray(new TestAssertionConfig[old.sizeOfAssertionArray()]));
        config.setAttachmentArray(old.getAttachmentList().toArray(new AttachmentConfig[old.sizeOfAttachmentArray()]));
        XmlObject obj = old.getCredentials();
        if (obj != null) {
            config.setCredentials((CredentialsConfig) obj.copy());
        }
        obj = old.getParameters();
        if (obj != null) {
            config.setParameters((RestParametersConfig) obj.copy());
        }

        obj = old.getRequest();
        if (obj != null) {
            config.setRequest((CompressedStringConfig) obj.copy());
        }
        obj = old.getSettings();
        if (obj != null) {
            config.setSettings((SettingsConfig) obj.copy());
        }
        if (old.isSetDescription()) {
            config.setDescription(old.getDescription());
        }
        config.setEncoding(old.getEncoding());
        config.setEndpoint(old.getEndpoint());
        config.setSslKeystore(old.getSslKeystore());
        if (old.isSetMediaType()) {
            config.setMediaType(old.getMediaType());
        }
        if (old.isSetMethod()) {
            config.setMethod(old.getMethod());
        }
        if (old.isSetName()) {
            config.setName(old.getName());
        }
        if (old.isSetPostQueryString()) {
            config.setPostQueryString(old.getPostQueryString());
        }
        return config;
    }

    public static HttpRequestConfig updateIfNeeded(XmlObject config) {
        try {
            if (config instanceof RestRequestStepConfig) {
                return convert(OldRestRequestConfig.Factory.parse(config.selectChildren(
                        "http://eviware.com/soapui/config", "restRequest")[0].toString()));
            } else {
                return (HttpRequestConfig) config.changeType(HttpRequestConfig.type);
            }
        } catch (XmlException e) {
            return HttpRequestConfig.Factory.newInstance();
        }
    }

    public static class RestConversionException extends RuntimeException {
        public RestConversionException(String message) {
            super(message);
        }
    }

    public static void updateRestTestRequest(RestTestRequestStep restTestRequestStep) {
        try {
            RestRequestStepConfig restRequestStepConfig = (RestRequestStepConfig) restTestRequestStep.getConfig()
                    .getConfig();
            RestRequestConfig restRequestConfig = restRequestStepConfig.getRestRequest();
            OldRestRequestConfig oldConfig = OldRestRequestConfig.Factory.parse(restRequestConfig.toString());

            RestParametersConfig oldParams = oldConfig.getParameters();
            if (oldParams != null) {
                StringToStringMapConfig newParams = restRequestConfig.addNewParameters();

                for (RestParameterConfig oldParam : oldParams.getParameterList()) {
                    if (StringUtils.hasContent(oldParam.getValue())) {
                        Entry entry = newParams.addNewEntry();
                        entry.setKey(oldParam.getName());
                        entry.setValue(oldParam.getValue());
                    }
                }

                restRequestConfig.getParameters().getDomNode().getParentNode()
                        .removeChild(restRequestConfig.getParameters().getDomNode());
            }
        } catch (XmlException e) {
            SoapUI.logError(e);
        }
    }
}
